/*
 * @(#)PascalExample.java	1.0 Apr 26, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.math;

import org.eoti.math.prime.Sieve;
import org.eoti.util.size.Bytes;

import java.io.*;
import java.math.BigInteger;
import java.util.concurrent.*;
import java.util.*;
import java.util.zip.*;

public class PascalExample
{
	protected static boolean debug = false;

	public static void main(String[] args)
			throws IOException
	{
		if(System.getProperty("debug", "false").equalsIgnoreCase("true"))
			debug = true;

		if((args.length < 2)||(args.length > 3))
		{
			System.out.println("USAGE: java org.eoti.math.PascalExample COMMAND [options...]");
			System.out.println("WHERE COMMANDs are:");
			System.out.println("\tisprime NUMBER");
			System.out.println("\trange LOWNUM HIGHNUM");
			System.out.println("\tsave NUMBER FILENAME");
			System.out.println("\tload FILENAME AMOUNTTOSHOW");
			System.out.println("\tdisplay NUMBER");
			System.out.println("\ttiming NUMBER");
			System.exit(0);
		}

		String cmd = args[0].toLowerCase().trim();
		if(cmd.equals("isprime"))
			isPrime(args);
		else if(cmd.equals("range"))
			range(args);
		else if(cmd.equals("save"))
			save(args);
		else if(cmd.equals("load"))
			load(args);
		else if(cmd.equals("display"))
			display(args);
		else if(cmd.equals("timing"))
			timing(args);
		else
			System.out.println("Unknown command");
	}

	public static void timing(String[] args)
	{
		BigInteger value = new BigInteger(args[1]);
		timingTestOne(value);
		System.gc();
		timingTestTwo(value);
		System.gc();
//		timingTestThree(value);
//		System.gc();
		timingTestFour(value);
		System.gc();
	}

	private static void timingTestOne(BigInteger value)
	{
		long start = System.nanoTime();
		Pascal p = Pascal.toPascalUsingTriangle(value);
		long end = System.nanoTime();
		System.out.println("Found that " +value+ (p.isPrime()?" is":" is not") +" prime [by building a tree] in " + TimeUnit.NANOSECONDS.toMillis(end-start) + " milliseconds");
	}

	private static void timingTestTwo(BigInteger value)
	{
		long start = System.nanoTime();
		Pascal p = Pascal.toPascalUsingFactorials(value);
//		System.out.println(p.getValue()+"="+p.toArchivalString());
		long end = System.nanoTime();
		System.out.println("Found that " +value+ (p.isPrime()?" is":" is not") +" prime [by using factorials] in " + TimeUnit.NANOSECONDS.toMillis(end-start) + " milliseconds");
	}

	private static void timingTestThree(BigInteger value)
	{
		long start = System.nanoTime();
		Sieve s = new Sieve(value.add(BigInteger.ONE));
		ArrayList<BigInteger> primes = s.getPrimes();
//		int i=1;
//		for(BigInteger bi : primes)
//			System.out.println((i++)+" >" + bi);
		boolean isPrime = primes.contains(value);
		long end = System.nanoTime();
		System.out.println("Found that " +value+ (isPrime?" is":" is not") +" prime [by using Sieve] in " + TimeUnit.NANOSECONDS.toMillis(end-start) + " milliseconds");
	}

	private static void timingTestFour(BigInteger value)
	{
		long start = System.nanoTime();
		Pascal p = Pascal.toPascalUsingFractions(value);
		long end = System.nanoTime();
//		System.out.println(p.getValue()+"="+p.toArchivalString());
		System.out.println("Found that " +value+ (p.isPrime()?" is":" is not") +" prime [by using fractions] in " + TimeUnit.NANOSECONDS.toMillis(end-start) + " milliseconds");
	}

	public static void display(String[] args)
	{
		Pascal p = Pascal.toPascal(new BigInteger(args[1]));
		System.out.println(p.toArchivalString());
		System.out.println(" divided by " + p.getValue() + " = ");
		boolean first = true;
		for(BigInteger bi : p.toArray())
		{
			if(bi.equals(BigInteger.ONE))
			{
				if(first)
					System.out.print("n/a|");
				else
					System.out.println("n/a");

				first = false;
			}else{
				BigInteger[] dr = bi.divideAndRemainder(p.getValue());
				System.out.print(dr[0] + "r" + dr[1] + "|");
			}
		}
		System.out.println("");
	}

	public static void isPrime(String[] args)
	{
		Pascal p = Pascal.toPascal(new BigInteger(args[1]));
		System.out.println("Is " + p.getValue() + " prime? " + p.isPrime());
	}

	public static void range(String[] args)
	{
		Pascal p = Pascal.toPascal(new BigInteger(args[1]));
		BigInteger end = new BigInteger(args[2]);
		int i = 1;
		if(!p.isPrime())
			p = p.nextPrime();

		while(p.getValue().compareTo(end) <= 0)
		{
			System.out.print((i++)+"> " + p.getValue());
			if(debug)
				System.out.print("  " + getMemory());
			System.out.println();
			p = p.nextPrime();
		}
	}

	protected static Runtime runtime = Runtime.getRuntime();
	public static String getMemory()
	{
		long freemem = runtime.freeMemory();
		long totalmem = runtime.totalMemory();
		return getMemory(freemem) + "/" + getMemory(totalmem);
	}

	public static String getMemory(long amount)
	{
        return Bytes.friendly(amount);
	}

	public static void save(String[] args)
			throws IOException
	{
		Pascal p = Pascal.toPascal(new BigInteger(args[1]));
		PrintWriter out = new PrintWriter(new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(args[2]))));
//			PrintWriter out = new PrintWriter(new FileWriter(new File(args[2])));
		out.print(p.getValue()+"="+p.toArchivalString());
		out.flush();
		out.close();
	}

	public static void load(String[] args)
			throws IOException
	{
		Properties props = new Properties();
		props.load(new GZIPInputStream(new FileInputStream(args[1])));
//			props.load(new FileInputStream(args[1]));
		Enumeration e = props.propertyNames();
		String key = (String)e.nextElement();
		String value = (String)props.get(key);
		Pascal p = Pascal.fromArchivalString(value);
//			System.out.println("Loaded " + p.getValue());
		int i = 1;
		int end = Integer.parseInt(args[2]);
		if(!p.isPrime())
			p = p.nextPrime();
		while(i < end)
		{
			System.out.print((i++)+"> " + p.getValue());
			if(debug)
				System.out.print("  " + getMemory());
			System.out.println();
			p = p.nextPrime();
		}
	}
}
